package com.ynet.middleground.mobile.approve.service;

import static com.ynet.middleground.approve.constant.VotingVariables.*;
import static com.ynet.middleground.approve.constant.WorkflowConstants.FLOW_TASK_DONE_MOBILE_PROCESS;
import static com.ynet.middleground.approve.constant.WorkflowConstants.USER_TASK_APPROVE_RESULT;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.dozer.Mapper;
import org.slf4j.helpers.MessageFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.base.Throwables;
import com.ynet.core.codec.mark.util.ObjectUtil;
import com.ynet.core.common.BaseReqObj;
import com.ynet.core.common.ServiceResult;
import com.ynet.core.exception.BusinessException;
import com.ynet.core.log.IfpLogger;
import com.ynet.core.util.IDUtil;
import com.ynet.middleground.approve.bean.*;
import com.ynet.middleground.approve.constant.OperatingTypeEnum;
import com.ynet.middleground.approve.constant.WorkflowConstants;
import com.ynet.middleground.approve.dto.*;
import com.ynet.middleground.approve.service.ProcessImageService;
import com.ynet.middleground.approve.service.ProcessInstanceService;
import com.ynet.middleground.approve.service.ProcessTaskService;
import com.ynet.middleground.mobile.approve.constant.ErrorMessageConstant;
import com.ynet.middleground.mobile.approve.constant.SignActionEnum;
import com.ynet.middleground.mobile.approve.constant.SystemConstant;
import com.ynet.middleground.mobile.approve.dao.ArReceiveOperateMqLogMapper;
import com.ynet.middleground.mobile.approve.dao.OperateMqLogMapper;
import com.ynet.middleground.mobile.approve.dao.TaskExtMapper;
import com.ynet.middleground.mobile.approve.entity.ArReceiveOperateMqLog;
import com.ynet.middleground.mobile.approve.entity.OperateMqLog;
import com.ynet.middleground.mobile.approve.entity.TaskExt;
import com.ynet.middleground.mobile.approve.entity.TaskOperateStage;
import com.ynet.middleground.mobile.approve.mq.producer.SourceMobileApproveProducer;
import com.ynet.middleground.mobile.approve.request.*;
import com.ynet.middleground.mobile.approve.response.*;
import com.ynet.middleground.mobile.approve.utils.CommUtils;
import com.ynet.middleground.user.dto.ChannelDTO;
import com.ynet.middleground.user.dto.UserInfoDto;

/**
 * @ClassName ApproveCenterService
 * @Description 审批中心
 * @Author liwq
 * @Date 2021/3/19 15:11
 * @Version 1.0
 */
@Service
public class ApproveCenterService {

    @Reference(version = "0.0.1", check = false)
    ProcessTaskService processTaskService;

    @Reference(version = "0.0.1", check = false)
    ProcessInstanceService processInstanceService;

    @Reference(version = "0.0.1", check = false)
    ProcessImageService processImageService;

    @Autowired
    UserCenterService userCenterService;

    @Autowired
    private Mapper dozerBeanMapper;

    @Autowired
    private AuthService authService;

    @Autowired
    private TaskOperateStageService taskOperateStageService;

    @Autowired
    private SourceMobileApproveProducer sourceMobileApproveProducer;

    @Autowired
    private TaskExtMapper taskExtMapper;

    @Autowired
    private ArReceiveOperateMqLogMapper arReceiveOperateMqLogMapper;

    @Autowired
    private OperateMqLogMapper operateMqLogMapper;

    /**
     * mq任务备注类型
     */
    private static final String mqTaskType = "mq";

    /**
     * 一般任务备注类型
     */
    private static final String commentType = "comment";

    /**
     * 转办任务类型
     */
    private static final String transferType = "transfer";

    /**
     * 待审批类型
     */
    private static final String waitType = "wait";

    /**
     * 查询任务详情信息
     *
     * @param userId
     * @param taskId
     * @return
     */
    public TaskDTO getTask(Integer userId, String taskId) {
        QueryTaskDetailReq req = new QueryTaskDetailReq();
        ServiceResult<TaskDTO> serviceResult = new ServiceResult<>();
        TaskDTO taskDTO = new TaskDTO();
        req.setTraceId(IDUtil.getSecureRandomId());
        req.setOperationUserId(userId);
        req.setRequestTime(new Date());
        req.setChannel(SystemConstant.CHANNEL);
        req.setTaskId(taskId);
        try {
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批调用查询任务服务, reqParams: {}",
                req.toString());
            serviceResult = processTaskService.getTask(req);
            if (serviceResult != null) {
                taskDTO = serviceResult.getResult();
            }
            if (taskDTO == null) {
                throw new BusinessException(ErrorMessageConstant.ECUC0022_MESSAGE, ErrorMessageConstant.ECUC0022_CODE);
            }
        } catch (BusinessException e) {
            throw new BusinessException(e.getMessage(), e.getCode());
        } catch (Exception e) {
            IfpLogger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批调用查询任务详情信息服务异常, case: {}",
                Throwables.getStackTraceAsString(e));
            throw new BusinessException(ErrorMessageConstant.ECUC0020_MESSAGE, ErrorMessageConstant.ECUC0020_CODE);
        }
        return taskDTO;
    }

    /**
     * 分页查询审核任务列表
     *
     * @param req
     * @return 任务列表
     */
    public List<TaskListDTO> listTaskByPage(QueryTaskPagerReq req) {
        // step1: 准备参数
        QueryTaskForMobileReqPager reqPager = new QueryTaskForMobileReqPager();
        reqPager.setChannel(SystemConstant.CHANNEL);
        reqPager.setQueryChannel(req.getChannel());
        reqPager.setOperationUserId(req.getUserId());
        reqPager.setTraceId(IDUtil.getSecureRandomId());
        reqPager.setRequestTime(new Date());
        reqPager.setPagerInfo(req.getPageInfo());
        reqPager.setAssignee(String.valueOf(req.getUserId()));
        reqPager.setType(req.getType());
        reqPager.setCondition(req.getSearchContent());
        reqPager.setDateOrder(req.getDateSortType());
        reqPager.setBeginDate(CommUtils.dateBegin(req.getBeginDate()));
        reqPager.setEndDate(CommUtils.dateEnd(req.getEndDate()));
        // add by liwq on 2023-03-13 此处为新增的业态ID查询条件
        reqPager.setFormat(req.getFormat());

        ServiceResultMobile<List<TaskDTOForMobile>> serviceResult;
        List<TaskDTOForMobile> taskDTOList = new ArrayList<TaskDTOForMobile>(10);
        List<TaskListDTO> taskListDTOList = new ArrayList<TaskListDTO>(10);
        PagerInfoCount pagerInfo = req.getPageInfo();

        // step2: 转调审批中心
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批调用分页查询任务服务,reqParams: {}",
            req.toString());
        serviceResult = processTaskService.listTaskByPageForMobile(reqPager);

        // step3: 整理并返回
        if (!StringUtils.isEmpty(serviceResult.getErrorMsg())) {
            throw new BusinessException(serviceResult.getErrorMsg(), serviceResult.getErrorCode());
        }
        taskDTOList = serviceResult.getResult();
        final Map<String, UserInfoDto> userCache = new HashMap<>(16);
        final Map<String, UserDTO> dingTalkUserCache = new HashMap<>(16);
        final Map<String, ChannelDTO> channelDTOCache = new HashMap<>(16);
        if (taskDTOList != null && taskDTOList.size() > 0) {
            taskDTOList.stream().forEach(t -> {
                TaskListDTO taskListDTO = new TaskListDTO();
                // 将是否等待指令、id等相同字段转换到taskDataDTO
                taskListDTO = dozerBeanMapper.map(t, TaskListDTO.class);
                // 从流程变量里取出某些值放到taskDataDTO
                Map<String, Object> processVariable = t.getProcessVariables();
                if (processVariable != null) {
                    // 流程名称
                    taskListDTO
                        .setProcessDefinitionName((String)processVariable.get(WorkflowConstants.FLOW_PROCESS_NAME));
                    // 流程发起人ID
                    String starterId = (String)processVariable.get(WorkflowConstants.START_USER_ID);
                    taskListDTO.setStartUserId(starterId);
                    // 流程发起人名称
                    taskListDTO.setStartUserName((String)processVariable.get(WorkflowConstants.FLOW_START_USER_NAME));
                    // 流程发起人所属公司
                    taskListDTO.setStartUserCompany((String)processVariable.get(WorkflowConstants.FLOW_COMPANY_NAME));
                    // 流程发起渠道
                    taskListDTO.setStartChannel((String)processVariable.get(WorkflowConstants.CHANNEL_SIGNAL));
                    // 流程名称
                    taskListDTO
                        .setProcessDefinitionName((String)processVariable.get(WorkflowConstants.FLOW_PROCESS_NAME));
                    // 流程发起渠道名称
                    ChannelDTO channelDTO =
                        channelDTOCache.get((String)processVariable.get(WorkflowConstants.CHANNEL_SIGNAL));
                    if (channelDTO == null) {
                        channelDTO = userCenterService.getChannelDetail(
                            (String)processVariable.get(WorkflowConstants.CHANNEL_SIGNAL), req.getUserId());
                        channelDTOCache.put((String)processVariable.get(WorkflowConstants.CHANNEL_SIGNAL), channelDTO);
                    }
                    taskListDTO.setStartChannelName(channelDTO == null ? null : channelDTO.getChannelName());
                    // 流程发起人头像
                    if (StringUtils.isNotBlank(starterId)) {
                        UserInfoDto userInfo = userCache.get(starterId);
                        if (userInfo == null) {
                            userInfo = userCenterService.getUserInfo(Integer.parseInt(starterId),
                                SystemConstant.CHANNEL, Integer.parseInt(starterId), null);
                            userCache.put(starterId, userInfo);
                        }
                        if (userInfo != null && userInfo.getUserBaseInformationDto() != null) {
                            UserDTO userDTO = dingTalkUserCache.get(starterId);
                            if (userDTO == null) {
                                if (!StringUtils.isBlank(userInfo.getUserBaseInformationDto().getDingtalkUserid())) {
                                    userDTO =
                                        authService.getUser(userInfo.getUserBaseInformationDto().getDingtalkUserid());
                                    dingTalkUserCache.put(starterId, userDTO);
                                }
                            }
                            taskListDTO.setStartUserAvatarUrl(userDTO == null ? null : userDTO.getAvatar());
                        }
                    }
                    // 流程优先级
                    taskListDTO
                        .setPriority(String.valueOf(processVariable.get(WorkflowConstants.FLOW_PROCESS_PRIORITY)));
                    // 是否已读
                    taskListDTO.setIsRead(t.getIsReadFlag());
                    // 金控经办人员
                    String qdpfOperatorId = null;
                    qdpfOperatorId = String.valueOf(processVariable.get(WorkflowConstants.FLOW_BUSINESS_QDPF_OPERATOR));
                    taskListDTO.setQdpfOperator(qdpfOperatorId);
                    // 金控经办人员姓名
                    taskListDTO.setQdpfOperatorName(
                        (String)processVariable.get(WorkflowConstants.FLOW_BUSINESS_QDPF_OPERATOR_NAME));
                    // 金控经办人员钉钉URL
                    String qdpfDingtalkId =
                        (String)processVariable.get(WorkflowConstants.FLOW_BUSINESS_QDPF_DINGTALK_USERID);
                    if (StringUtils.isNotEmpty(qdpfDingtalkId)) {
                        UserDTO qdpfUserDTO = dingTalkUserCache.get(qdpfOperatorId);
                        if (qdpfUserDTO == null) {
                            qdpfUserDTO = authService.getUser(qdpfDingtalkId);
                            dingTalkUserCache.put(qdpfOperatorId, qdpfUserDTO);
                        }
                        taskListDTO.setQdpfOperatorAvatarUrl(qdpfUserDTO == null ? null : qdpfUserDTO.getAvatar());
                    }
                    // 流程摘要
                    taskListDTO.setProcessSummary((String)processVariable.get(WorkflowConstants.FLOW_BUSINESS_SUMMARY));
                    // add by liwq on 2023-03-13 start
                    // 若审批数据存在实际业务发起时间，则使用实际业务发起时间，若不存在，则使用流程发起时间；
                    String businessStartTime =
                        // String.valueOf(processVariable.get(WorkflowConstants.FLOW_BUSINESS_START_TIME));
                        String.valueOf(processVariable.get(WorkflowConstants.FLOW_START_TIME));
                    taskListDTO.setStartDate(
                        !StringUtils.isEmpty(businessStartTime) ? businessStartTime : taskListDTO.getStartDate());
                    // add by liwq on 2023-03-13 end
                }
                taskListDTOList.add(taskListDTO);
            });
        }
        pagerInfo.setTotal(serviceResult.getPagerInfo().getTotal());
        pagerInfo.setToDoTaskCount(serviceResult.getPagerInfo().getToDoTaskCount());
        return taskListDTOList;
    }

    /**
     * 整理流程审批记录
     *
     * @param req
     * @return
     */
    public ApproveRecordDTO getApproveRecords(ApproveRecordsReq req) {
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批【查询流程审批记录】服务，req：{}", req);
        // Step1：变量准备
        // 接收审批中台返回的数据用的变量
        ServiceResult<List<HistoryTaskDTO>> result = new ServiceResult<>();
        List<HistoryTaskDTO> list = new ArrayList<HistoryTaskDTO>(10);
        // 返回给移动审批前端用的变量
        ApproveRecordDTO response = new ApproveRecordDTO();
        List<HistoryTaskRecordDTO> taskRecords = new ArrayList<>(10);
        // 用于临时存放会签任务Map
        Map<String, List<HistoryTaskDTO>> votingTasks = new HashMap<>();
        // step2: 转调审批中台
        result = listHistoryTaskByProcessInstanceId(req.getUserId(), req.getProcessInstanceId());
        list = result.getResult();

        // step3: 处理结果并返回
        // 处理到第几条标记
        Integer executeIndex = 0;
        Integer total = list.size();
        Map<String, UserInfoDto> userCache = new HashMap<>(16);
        Map<String, UserDTO> dingTalkUserCache = new HashMap<>(16);
        for (HistoryTaskDTO h : list) {
            executeIndex++;
            Map<String, Object> localVariables = h.getTaskLocalVariables();
            if (localVariables != null) {
                String flow_voting_flag = (String)localVariables.get(VOTING_FLAG);
                // 会签任务Map的value值
                List<HistoryTaskDTO> tempValues = new ArrayList<>(10);
                // step3.1:
                // 任务变量里的会签标记字段不为空，表示该任务是会签;
                // 把会签任务先用map保存一下
                if (!StringUtils.isEmpty(flow_voting_flag)) {
                    // step3.1.1
                    // 先从map里取出flow_voting_flag，若有值，代表会签节点的其中一个执行任务已经放到map了，
                    // 取出来，放到taskValues暂存
                    if (votingTasks.get(flow_voting_flag) != null && votingTasks.get(flow_voting_flag).size() > 0) {
                        tempValues.addAll(votingTasks.get(flow_voting_flag));
                    }
                    // step3.1.2
                    // 再将本次的会签执行任务放到taskValues暂存
                    tempValues.add(h);
                    // step3.1.2
                    // 最后一并再放到map
                    votingTasks.put(flow_voting_flag, tempValues);
                }
                // step3.2:
                // 任务变量里的会签标记字段为空，表示该任务不是会签
                if (StringUtils.isEmpty(flow_voting_flag)) {
                    // step3.2.1: 会签map有值的话，先把之前整理的会签任务整理到responseList
                    if (votingTasks != null && votingTasks.size() > 0) {
                        List<HistoryTaskRecordDTO> historyTasks =
                            dealVotingTasks(votingTasks, userCache, dingTalkUserCache);
                        taskRecords.addAll(historyTasks);
                    }

                    // step3.2.2: 再将本次任务整理到responseList
                    List<HistoryTaskRecordDTO> tasks = dealCommonTasks(h, userCache, dingTalkUserCache);

                    taskRecords.addAll(tasks);
                }
                // step3.3: 把最后一个会签节点的数据组装到返回数据里
                if (executeIndex.equals(total) && votingTasks != null && votingTasks.size() > 0) {
                    List<HistoryTaskRecordDTO> historyTasks =
                        dealVotingTasks(votingTasks, userCache, dingTalkUserCache);
                    taskRecords.addAll(historyTasks);
                }
            }
        }
        Boolean isFinish = isFinishProcess(req.getUserId(), req.getProcessInstanceId());
        response.setTaskRecords(taskRecords);
        response.setProcessEnd(isFinish);
        response.setProcessDurationOfMillis(getProcessDurationOfMillis(isFinish, list));
        return response;
    }

    /**
     * 调用审批中心-根据流程实例ID查询流程任务处理记录服务
     *
     * @param userId
     * @param processInstanceId
     * @return
     */
    public ServiceResult<List<HistoryTaskDTO>> listHistoryTaskByProcessInstanceId(Integer userId,
        String processInstanceId) {
        // step1: 准备参数
        ProcessInstanceReq req = new ProcessInstanceReq();
        req.setTraceId(IDUtil.getSecureRandomId());
        req.setChannel(SystemConstant.CHANNEL);
        req.setOperationUserId(userId);
        req.setRequestTime(new Date());
        req.setProcessInstanceId(processInstanceId);
        ServiceResult<List<HistoryTaskDTO>> result = new ServiceResult<>();

        // step2: 调用并返回结果
        result = processInstanceService.listHistoryTaskByProcessInstanceId(req);

        return result;

    }

    /**
     * 任务结束的话，返回任务持续时间
     *
     * @param isFinish
     * @param list
     * @return 任务持续时间（时间戳）
     */
    public String getProcessDurationOfMillis(Boolean isFinish, List<HistoryTaskDTO> list) {
        if (isFinish) {
            Date processStart = list.get(0).getStartTime();
            Date processEnd = list.get(list.size() - 1).getEndTime();
            String durationOfMillis = CommUtils.formatTime(processEnd.getTime() - processStart.getTime());
            return durationOfMillis;
        }
        return null;
    }

    /**
     * 判断流程是否结束
     *
     * @param userId
     * @param processInstanceId
     * @return true 结束；false 未结束
     */
    public boolean isFinishProcess(Integer userId, String processInstanceId) {
        // step1： 参数准备
        ProcessInstanceReq req = new ProcessInstanceReq();
        req.setTraceId(IDUtil.getSecureRandomId());
        req.setChannel(SystemConstant.CHANNEL);
        req.setOperationUserId(userId);
        req.setRequestTime(new Date());
        req.setProcessInstanceId(processInstanceId);
        ServiceResult<ProcessInstanceDTO> result = null;

        // step2: 转调审批中心
        result = processInstanceService.isFinishProcess(req);

        // step3: 返回
        return result.getResult() == null;
    }

    /**
     * 处理一般任务
     *
     * @param taskDTO
     * @return 审批记录-已处理任务记录
     */
    public List<HistoryTaskRecordDTO> dealCommonTasks(HistoryTaskDTO taskDTO, Map<String, UserInfoDto> userCache,
        Map<String, UserDTO> dingTalkUserCache) {
        List<HistoryTaskRecordDTO> dealCommonTasks = new ArrayList<>(10);
        // 整理备注和mq消息
        List<CommentDTO> commentDTOS = getComments(taskDTO);
        // 审批记录-已处理任务记录
        HistoryTaskRecordDTO history = new HistoryTaskRecordDTO();

        history.setIsCountersign(false);
        List<DealedRecordDTO> dealedRecordDTOS = dealedComments(commentDTOS, taskDTO, userCache, dingTalkUserCache);
        history.setRecords(dealedRecordDTOS);
        dealCommonTasks.add(history);
        return dealCommonTasks;
    }

    /**
     * 处理任务备注
     *
     * @return
     */
    public List<DealedRecordDTO> dealedComments(List<CommentDTO> commentDTOS, HistoryTaskDTO taskDTO,
        Map<String, UserInfoDto> userCache, Map<String, UserDTO> dingTalkUserCache) {
        // 审批人Id
        String assigneeId = null;
        // 转办至Id
        String transferToId = null;
        // 转办至姓名
        String transferToName = null;
        // fullmessage样例，若是转办，显示：550,转办至,549
        String fullmessage = null;
        // 任务变量
        Map<String, Object> localVariables = taskDTO.getTaskLocalVariables();

        // 审批记录-已处理任务记录列表-处理记录列表
        List<DealedRecordDTO> dealedRecordDTOS = new ArrayList<>(10);

        // 处理序号
        int dealIndex = -1;
        for (CommentDTO c : commentDTOS) {
            // 审批记录-已处理任务记录列表-处理记录
            DealedRecordDTO dealedRecordDTO = new DealedRecordDTO();

            dealIndex = dealIndex + 1;
            // ①转办操作
            if (transferType.equals(c.getType())) {
                fullmessage = c.getFullMessage();
                if (StringUtils.isNotEmpty(fullmessage)) {
                    int firstFlagIndex = fullmessage.indexOf(",");
                    int lastFlagIndex = fullmessage.lastIndexOf(",");
                    if (firstFlagIndex > -1) {
                        assigneeId = fullmessage.substring(0, firstFlagIndex);
                    }
                    if (lastFlagIndex > -1) {
                        transferToId = fullmessage.substring(lastFlagIndex + 1);
                    }
                }
            }
            // ②一般操作 && mq操作
            if (commentType.equals(c.getType()) || mqTaskType.equals(c.getType()) || waitType.equals(c.getType())) {
                assigneeId = taskDTO.getAssignee();
            }

            // 任务Id
            dealedRecordDTO.setId(taskDTO.getId());
            // 是否读取
            dealedRecordDTO.setReadState(taskDTO.getReadState());
            // 任务名称
            dealedRecordDTO.setName(taskDTO.getName());
            // 任务审批人ID
            dealedRecordDTO.setAssignee(assigneeId);
            // 任务审批人名称&审批人头像地址
            if (StringUtils.isNotBlank(assigneeId)) {
                UserInfoDto userInfo = userCache.get(assigneeId);
                if (userInfo == null) {
                    userInfo = userCenterService.getUserInfo(Integer.parseInt(assigneeId), SystemConstant.CHANNEL,
                        Integer.parseInt(assigneeId), null);
                    if (userInfo != null && userInfo.getUserBaseInformationDto() != null) {
                        userCache.put(assigneeId, userInfo);
                    }
                }
                if (userInfo != null && userInfo.getUserBaseInformationDto() != null) {
                    dealedRecordDTO.setAssigneeName(userInfo.getUserBaseInformationDto().getUserName());
                    UserDTO userDTO = dingTalkUserCache.get(assigneeId);
                    if (userDTO == null) {
                        if (StringUtils.isNotBlank(userInfo.getUserBaseInformationDto().getDingtalkUserid())) {
                            userDTO = authService.getUser(userInfo.getUserBaseInformationDto().getDingtalkUserid());
                            dingTalkUserCache.put(assigneeId, userDTO);
                        }
                    }
                    dealedRecordDTO.setAssigneeAvatarUrl(userDTO == null ? null : userDTO.getAvatar());
                }
            }
            // 是否来自移动端
            Boolean fromMobile = (Boolean)localVariables.get(FLOW_TASK_DONE_MOBILE_PROCESS);
            if (fromMobile != null && fromMobile == true) {
                dealedRecordDTO.setIsFromMobile(true);
            } else {
                dealedRecordDTO.setIsFromMobile(false);
            }
            // 转办类型处理
            if (transferType.equals(c.getType())) {
                dealedRecordDTO.setApproveResult(OperatingTypeEnum.TRANSFER.getCode());
                if (StringUtils.isNotEmpty(transferToId)) {
                    UserInfoDto userInfo = userCache.get(transferToId);
                    if (userInfo == null) {
                        userInfo = userCenterService.getUserInfo(Integer.parseInt(transferToId), SystemConstant.CHANNEL,
                            Integer.parseInt(transferToId), null);
                        if (userInfo != null && userInfo.getUserBaseInformationDto() != null) {
                            userCache.put(transferToId, userInfo);
                        }
                    }
                    if (userInfo != null && userInfo.getUserBaseInformationDto() != null) {
                        transferToName = userInfo.getUserBaseInformationDto().getUserName();
                        fullmessage = "转办至" + transferToName;
                        dealedRecordDTO.setApproveComment(fullmessage);
                    }
                }
                dealedRecordDTO.setBusinessOperateList(null);
            }
            // 一般类型处理
            if (commentType.equals(c.getType())) {
                dealedRecordDTO.setApproveResult((Integer)localVariables.get(USER_TASK_APPROVE_RESULT));
                dealedRecordDTO.setApproveComment(c.getFullMessage());
                // 业务操作记录集合
                String operateRecord =
                    (String)localVariables.get(WorkflowConstants.FLOW_OPERATE_TASK_OPERATE_RECORD_DATA);
                List<OperateRecord> businessOperateList = businessOperateList(operateRecord);
                dealedRecordDTO.setBusinessOperateList(businessOperateList);
            }
            // 2023-03-13 syf 增加返回处理意见附件
            if (c.getCommentExtDTO() != null) {
                dealedRecordDTO.setCommentFiles(JSON.parseArray(c.getCommentExtDTO().getFiles(), CommentFile.class));
            }
            // mq类型处理
            if (mqTaskType.equals(c.getType())) {
                LambdaQueryWrapper<OperateMqLog> query = new LambdaQueryWrapper<>();
                query.eq(OperateMqLog::getTraceId, c.getId());
                List<OperateMqLog> operateMqLogs = operateMqLogMapper.selectList(query);
                if (operateMqLogs != null && operateMqLogs.size() > 0) {
                    dealedRecordDTO.setApproveResult(Integer.parseInt(operateMqLogs.get(0).getApproveOperate()));
                    dealedRecordDTO.setApproveComment(operateMqLogs.get(0).getComment());
                    dealedRecordDTO
                        .setCommentFiles(JSON.parseArray(operateMqLogs.get(0).getCommentFiles(), CommentFile.class));
                    // 业务操作记录集合
                    String operateRecord = operateMqLogs.get(0).getBusinessOperate();
                    List<OperateRecord> businessOperateList = businessOperateList(operateRecord);
                    dealedRecordDTO.setBusinessOperateList(businessOperateList);
                }
                dealedRecordDTO.setChannelRejectReason(c.getFullMessage());
            }
            if (waitType.equals(c.getType())) {
                dealedRecordDTO.setStartTime(String.valueOf(taskDTO.getStartTime().getTime()));
            }
            // 是执行的第一条，开始时间取任务开始时间,结束时间取当前备注时间
            if (dealIndex == 0 && !waitType.equals(c.getType())) {
                Long startTime = taskDTO.getStartTime() == null ? 0L : taskDTO.getStartTime().getTime();
                Long endTime = commentDTOS.get(dealIndex) == null ? 0L : commentDTOS.get(dealIndex).getTime().getTime();
                dealedRecordDTO.setStartTime(String.valueOf(startTime));
                dealedRecordDTO.setEndTime(String.valueOf(endTime));
                // 持续时间（毫秒）
                if (endTime > 0L) {
                    dealedRecordDTO.setDurationOfMillis(CommUtils.formatTime(endTime - startTime));
                }
            }
            // 不是第一条,开始时间取上一次备注时间,结束时间取当前备注时间
            if (dealIndex > 0 && !waitType.equals(c.getType())) {
                Long startTime =
                    commentDTOS.get(dealIndex - 1) == null ? 0L : commentDTOS.get(dealIndex - 1).getTime().getTime();
                Long endTime = commentDTOS.get(dealIndex) == null ? 0L : commentDTOS.get(dealIndex).getTime().getTime();
                dealedRecordDTO.setStartTime(String.valueOf(startTime));
                dealedRecordDTO.setEndTime(String.valueOf(endTime));
                // 持续时间（毫秒）
                if (endTime > 0L) {
                    dealedRecordDTO.setDurationOfMillis(CommUtils.formatTime(endTime - startTime));
                }
            }

            dealedRecordDTOS.add(dealedRecordDTO);
        }
        return dealedRecordDTOS;
    }

    /**
     * 将业务操作记录json,映射为对象
     *
     * @param jsonList
     * @return 业务操作记录对象
     */
    public List<OperateRecord> businessOperateList(String jsonList) {
        if (StringUtils.isEmpty(jsonList)) {
            return null;
        }
        List<OperateRecord> businessOperateList = null;
        try {
            businessOperateList = new ArrayList<>();
            JSONArray jsonArray = JSON.parseArray(jsonList);
            for (Object o : jsonArray) {
                JSONObject jsonObject = (JSONObject)o;
                OperateRecord business = JSONObject.toJavaObject(jsonObject, OperateRecord.class);
                businessOperateList.add(business);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return businessOperateList;
    }

    /**
     * 整理任务备注
     *
     * @return
     */
    List<CommentDTO> getComments(HistoryTaskDTO h) {
        // 任务备注
        List<CommentDTO> commentDTOS = new ArrayList<>(10);
        commentDTOS = h.getTaskComments();
        // 渠道端消息
        LambdaQueryWrapper<ArReceiveOperateMqLog> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ArReceiveOperateMqLog::getTaskId, h.getId());
        queryWrapper.eq(ArReceiveOperateMqLog::getAllowApproveOperate, 0);
        List<ArReceiveOperateMqLog> logs = arReceiveOperateMqLogMapper.selectList(queryWrapper);
        for (ArReceiveOperateMqLog l : logs) {
            CommentDTO c = new CommentDTO();
            c.setId(l.getPrevTraceId());
            c.setUserId(h.getAssignee());
            c.setTime(Date.from(l.getGmtCreate().atZone(ZoneId.systemDefault()).toInstant()));
            c.setTaskId(l.getTaskId());
            c.setProcessInstanceId(h.getProcessInstanceId());
            c.setTaskName(h.getName());
            c.setFullMessage(l.getReason());
            c.setType(mqTaskType);
            commentDTOS.add(c);
        }
        // 任务没结束，就追加一条
        if (h.getEndTime() == null) {
            CommentDTO c = new CommentDTO();
            c.setId(IDUtil.getSecureRandomId().substring(0, 8));
            c.setUserId(h.getAssignee());
            c.setTime(new Date());
            c.setTaskId(h.getId());
            c.setProcessInstanceId(h.getProcessInstanceId());
            c.setTaskName(h.getName());
            c.setFullMessage("");
            c.setType(waitType);
            commentDTOS.add(c);
        }
        commentDTOS =
            commentDTOS.stream().sorted(Comparator.comparing(CommentDTO::getTime)).collect(Collectors.toList());
        return commentDTOS;
    }

    /**
     * 处理会签节点任务
     *
     * @param votingTasks
     * @return 审批记录-已处理任务记录列表
     */
    public List<HistoryTaskRecordDTO> dealVotingTasks(Map<String, List<HistoryTaskDTO>> votingTasks,
        Map<String, UserInfoDto> userCache, Map<String, UserDTO> dingTalkUserCache) {
        if (votingTasks != null) {
            // 审批记录-已处理任务记录列表
            List<HistoryTaskRecordDTO> historyTaskRecordDTOS = new ArrayList<>(10);
            for (String key : votingTasks.keySet()) {
                // 会签是否结束,只要有一个会签任务endTime为null,就代表会签没结束
                boolean votingEndFlag = true;

                // 审批记录-已处理任务记录
                HistoryTaskRecordDTO history = new HistoryTaskRecordDTO();

                // 审批记录-已处理任务记录列表-处理记录列表
                List<DealedRecordDTO> dealedRecordDTOS = new ArrayList<>(10);
                List<HistoryTaskDTO> mapValues = votingTasks.get(key);
                if (mapValues != null && mapValues.size() > 0) {
                    for (HistoryTaskDTO h : mapValues) {
                        // 会签是否结束
                        if (h.getEndTime() == null) {
                            votingEndFlag = false;
                        }

                        // 整理备注和mq消息
                        List<CommentDTO> commentDTOS = getComments(h);
                        // 将备注和mq消息处理成记录
                        List<DealedRecordDTO> dealedComments =
                            dealedComments(commentDTOS, h, userCache, dingTalkUserCache);
                        dealedRecordDTOS.addAll(dealedComments);
                    }
                    dealedRecordDTOS = dealedRecordDTOS.stream()
                        .sorted(
                            Comparator.comparing(DealedRecordDTO::getEndTime, Comparator.nullsLast(String::compareTo)))
                        .collect(Collectors.toList());
                    history.setRecords(dealedRecordDTOS);
                }

                // 是否会签
                history.setIsCountersign(true);
                // 会签是否结束
                history.setCountersignEnd(votingEndFlag);
                // 会签开始时间/结束时间/持续时间
                Long durationOfMillis = 0L;
                Date counterSignStart = mapValues.get(0).getStartTime();
                Date counterSignEnd = mapValues.get(mapValues.size() - 1).getEndTime();
                if (votingEndFlag) {
                    durationOfMillis = counterSignEnd.getTime() - counterSignStart.getTime();
                }
                history.setStartTime(String.valueOf(counterSignStart.getTime()));
                if (votingEndFlag) {
                    history.setEndTime(String.valueOf(counterSignEnd.getTime()));
                    history.setDurationOfMillis(CommUtils.formatTime(durationOfMillis));
                }
                // 同意人数/失败人数
                // 取出已经审批的会签节点的最后一条数据的面的任务变量
                List<HistoryTaskDTO> taskDTOS = selectApprovedTask(mapValues);
                Map<String, Object> localVariables = null;
                Integer agree = 0;
                Integer disagree = 0;
                if (taskDTOS != null && taskDTOS.size() > 0) {
                    localVariables = taskDTOS.get(taskDTOS.size() - 1).getTaskLocalVariables();
                    agree = (Integer)localVariables.get(NUMBER_OF_AGREED_INSTANCES);
                    disagree = (Integer)localVariables.get(NUMBER_OF_DISAGREED_INSTANCES);
                }
                history.setAgreeNumber(agree);
                history.setDisagreeNumber(disagree);

                historyTaskRecordDTOS.add(history);
            }
            votingTasks.clear();
            return historyTaskRecordDTOS;
        }
        return null;
    }

    /**
     * 将会签节点里面，没有审批的任务剔除，并排序，按照结束时间正序排序
     *
     * @param tasks
     * @return
     */
    public List<HistoryTaskDTO> selectApprovedTask(List<HistoryTaskDTO> tasks) {
        List<HistoryTaskDTO> selectApprovedTask = tasks.stream()
            .filter(h -> h.getTaskLocalVariables().get(USER_TASK_APPROVE_RESULT) != null).collect(Collectors.toList());

        if (selectApprovedTask != null && selectApprovedTask.size() > 0) {
            selectApprovedTask = selectApprovedTask.stream().sorted(Comparator.comparing(HistoryTaskDTO::getEndTime))
                .collect(Collectors.toList());
        }
        return selectApprovedTask;
    }

    /**
     * 获取审批流程图
     *
     * @param req
     * @return 图片Base64格式
     */
    public ProcessBase64ImageDTO getBase64Image(GetBase64ImageReq req) {
        // step1: 准备参数
        GetImageReq getImageReq = new GetImageReq();
        getImageReq.setTraceId(IDUtil.getSecureRandomId());
        getImageReq.setChannel(SystemConstant.CHANNEL);
        getImageReq.setOperationUserId(req.getUserId());
        getImageReq.setRequestTime(new Date());
        getImageReq.setProcessInstanceId(req.getProcessInstanceId());
        ServiceResult<String> imageInfo = new ServiceResult<>();
        ProcessBase64ImageDTO processBase64ImageDTO = new ProcessBase64ImageDTO();

        // step2: 转调审批中心
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批调用获取审批流程图服务,reqParams: {}",
            req.toString());
        imageInfo = processImageService.getImageByInstanceId(getImageReq);
        // step3: 整理并返回
        String imageBase64 = imageInfo.getResult();
        if (!StringUtils.isEmpty(imageBase64)) {
            processBase64ImageDTO.setImage(imageBase64);
        }

        return processBase64ImageDTO;
    }

    /**
     * 驳回-提交
     *
     * @param taskOperateStage 任务记录
     */
    public ServiceResult<Object> invokeApproveReject(TaskOperateStage taskOperateStage,
        OperatingTypeEnum operatingTypeEnum) {
        // step1: 准备参数
        // 普通参数
        ServiceResult<Object> result = new ServiceResult<>();
        RecallTaskReq recallTaskReq = new RecallTaskReq();
        recallTaskReq.setTraceId(IDUtil.getSecureRandomId());
        recallTaskReq.setChannel(SystemConstant.CHANNEL);
        recallTaskReq.setOperationUserId(taskOperateStage.getAssignee());
        recallTaskReq.setRequestTime(new Date());
        recallTaskReq.setCurrentTaskId(taskOperateStage.getTaskId());
        recallTaskReq.setReason(taskOperateStage.getComment());
        // recallTaskReq.setJumpPurpose(OperatingTypeEnum.REJECT_TO_STARTER.getCode());
        recallTaskReq.setJumpPurpose(operatingTypeEnum.getCode());
        // 退回就使用 3，否则不传数值，审批中心默认会用 6
        recallTaskReq.setFlowFromTaskOperate(3 == operatingTypeEnum.getCode() ? operatingTypeEnum.getCode() : null);
        recallTaskReq.setCommentFiles(StringUtils.isNotEmpty(taskOperateStage.getCommentFiles())
            ? JSON.parseArray(taskOperateStage.getCommentFiles(), CommentFile.class) : null);

        // modify by liulx on 2023-03-17 | start
        HistoryTaskDTO rejectTaskInfo =
            getRejectTaskInfo(taskOperateStage.getAssignee(), taskOperateStage.getProcessInstanceId());
        if (rejectTaskInfo == null) {
            throw new BusinessException("无可驳回或退回的节点，请检查！", "ECMA0010");
        }

        Map<String, Object> taskVar = new HashMap<>(2);
        // 设置移动端处理标志
        taskVar.put(WorkflowConstants.FLOW_TASK_DONE_MOBILE_PROCESS, true);
        recallTaskReq.setVariables(taskVar);
        recallTaskReq.setTargetTaskKeyId(rejectTaskInfo.getTaskDefinitionKey());
        // modify by liulx on 2023-03-17 | end

        // step2: 转调审批中台，并将结果返回
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(),
            "移动审批调用自定义节点跳转服务, operatingTypeEnum 操作: {} ,reqParams: {}", operatingTypeEnum, recallTaskReq);
        result = processTaskService.customNodeJump(recallTaskReq);

        return result;
    }

    /**
     * 查询并获取配置了 firstNode 参数的任务节点
     * 
     * @param assignee 当前流程任务处理人
     * @param processInstanceId 流程实例 id
     * @return 可驳回的任务节点
     */
    public HistoryTaskDTO getRejectTaskInfo(Integer assignee, String processInstanceId) {
        ServiceResult<List<HistoryTaskDTO>> historyRecordResult =
            listHistoryTaskByProcessInstanceId(assignee, processInstanceId);
        if (historyRecordResult.getResult() != null && !historyRecordResult.getResult().isEmpty()) {
            // targetKeyId = historyRecordResult.getResult().get(0).getTaskDefinitionKey();
            // 驳回时，应检查任务是否有 firstNode 标识的任务，需要驳回至配置该参数的任务节点
            Optional<HistoryTaskDTO> optional = historyRecordResult.getResult().stream()
                .filter(task -> task.getBusinessOperateVariables().containsKey(SystemConstant.KEY_BUSINESS_FIRST_NODE))
                .findFirst();
            return optional.orElse(null);
        }
        return null;
    }

    /**
     * 查询任务详情信息
     *
     * @param taskId 任务 id
     * @param userId 当前用户 id
     * @author liulx
     * @date 2021-03-23 14:40:10
     */
    public TaskDetailDTO getTaskDetailById(String taskId, Integer userId, String dingtalkUserId) {
        QueryTaskDetailReq queryTaskDetailReq = new QueryTaskDetailReq();
        queryTaskDetailReq.setTaskId(taskId);
        queryTaskDetailReq.setTraceId(IDUtil.getSecureRandomId());
        queryTaskDetailReq.setChannel(SystemConstant.CHANNEL);
        queryTaskDetailReq.setOperationUserId(userId);
        queryTaskDetailReq.setRequestTime(new Date());
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批调用查询任务服务,reqParams: {}",
            queryTaskDetailReq.toString());
        ServiceResult<TaskDTO> serviceResultTask = processTaskService.getTask(queryTaskDetailReq);
        if (!serviceResultTask.isSuccess()) {
            throw new BusinessException(serviceResultTask.getErrorCode(), serviceResultTask.getErrorMsg());
        }
        TaskDTO taskDTO = serviceResultTask.getResult();

        TaskDetailDTO detailDTO = new TaskDetailDTO();
        detailDTO.setIsRequireUpload(false);
        detailDTO.setIsRequireSign(false);
        detailDTO.setIsRequireNextAssignee(false);
        detailDTO.setId(taskDTO.getId());
        detailDTO.setAssignee(taskDTO.getAssignee());
        detailDTO.setName(taskDTO.getName());
        detailDTO.setDescription(taskDTO.getDescription());
        detailDTO.setProcessInstanceId(taskDTO.getProcessInstanceId());
        Map<String, Object> taskVariables = taskDTO.getVariables();
        Map<String, Object> processVariables = taskDTO.getProcessVariables();
        detailDTO.setStartUserId(ObjectUtil.objectToString(processVariables.get(WorkflowConstants.START_USER_ID)));
        detailDTO
            .setStartUserName(ObjectUtil.objectToString(processVariables.get(WorkflowConstants.FLOW_START_USER_NAME)));
        detailDTO
            .setStartUserCompany(ObjectUtil.objectToString(processVariables.get(WorkflowConstants.FLOW_COMPANY_NAME)));
        detailDTO.setProcessDefinitionName(
            ObjectUtil.objectToString(processVariables.get(WorkflowConstants.FLOW_PROCESS_NAME)));
        detailDTO.setStartDate(ObjectUtil.objectToString(processVariables.get(WorkflowConstants.FLOW_START_TIME)));
        Object priority = processVariables.get(WorkflowConstants.FLOW_PROCESS_PRIORITY);
        detailDTO.setPriority(priority == null ? 0 : Integer.parseInt(String.valueOf(priority)));

        Object businessData = processVariables.get(WorkflowConstants.FLOW_BUSINESS_DATA);
        if (businessData instanceof List) {
            /* List<BusinessModuleData> businessModuleData =
                JSON.parseArray(JSON.toJSONString(businessData), BusinessModuleData.class);
            detailDTO.setBusinessData(businessModuleData);*/
            detailDTO.setBusinessData((List<BusinessModuleData>)businessData);
        }

        // 设置金控经办人员信息
        Object qdpfUserId = processVariables.get(WorkflowConstants.FLOW_BUSINESS_QDPF_OPERATOR);
        if (qdpfUserId != null) {
            detailDTO.setQdpfOperator(String.valueOf(qdpfUserId));
            detailDTO.setQdpfOperatorName(
                String.valueOf(processVariables.get(WorkflowConstants.FLOW_BUSINESS_QDPF_OPERATOR_NAME)));
            Object qdpfDingtalkUserId = processVariables.get(WorkflowConstants.FLOW_BUSINESS_QDPF_DINGTALK_USERID);
            if (qdpfDingtalkUserId != null) {
                // 用户头像
                // UserDTO userDTO = authService.getUser(dingtalkUserId);
                UserDTO userDTO = authService.getUser(String.valueOf(qdpfDingtalkUserId));
                detailDTO.setQdpfOperatorAvatarUrl(userDTO == null ? null : userDTO.getAvatar());
            }
        }
        // 查询发起人钉钉信息
        UserInfoDto userInfo = userCenterService.getUserInfo(Integer.valueOf(detailDTO.getStartUserId()),
            SystemConstant.CHANNEL, userId, null);
        if (userInfo != null && userInfo.getUserBaseInformationDto() != null
            && StringUtils.isNotBlank(userInfo.getUserBaseInformationDto().getDingtalkUserid())) {
            // 发起人用户头像
            UserDTO userDTO = authService.getUser(userInfo.getUserBaseInformationDto().getDingtalkUserid());
            detailDTO.setStartUserAvatarUrl(userDTO == null ? null : userDTO.getAvatar());
        }

        // 查询任务是否是正在处理中
        TaskOperateStage taskOperateStage = taskOperateStageService.getTaskOperateStageByTaskId(taskId);
        // 只有查询到既有临时记录且中台处理未完成的才是正在处理中
        detailDTO.setIsWait((taskOperateStage != null && taskOperateStage.getIsAllow() == null)
            || (taskOperateStage != null && taskOperateStage.getIsAllow() && taskOperateStage.getIsToBeSigned()));

        BaseReqObj baseReqObj = dozerBeanMapper.map(queryTaskDetailReq, BaseReqObj.class);
        ProcessInstanceDTO processInstanceDTO = checkProcessEnd(detailDTO.getProcessInstanceId(), baseReqObj);
        detailDTO.setProcessInstanceEnd(processInstanceDTO == null);
        /*  // 先判断流程变量中是否有用印流程的参数
        if (processVariables.containsKey(SystemConstant.KEY_BUSINESS_TIP_USER_SEAL)) {
          detailDTO.setSpecialTips(SystemConstant.MSG_BUSINESS_TIP_USER_SEAL);
        }*/
        Object businessOperate = taskVariables.get(WorkflowConstants.FLOW_BUSINESS_OPERATE_DATA);
        if ((businessOperate instanceof Map)) {
            Map<String, Object> businessOperateMap = (Map<String, Object>)businessOperate;

            // 如果任务变量中也有用印流程的参数，则使用任务中
            if (businessOperateMap.containsKey(SystemConstant.KEY_BUSINESS_TIP_USER_SEAL)) {
                detailDTO.setSpecialTips(SystemConstant.MSG_BUSINESS_TIP_USER_SEAL);
            }
            // 判断是否有要求上传附件
            if (businessOperateMap.containsKey(SystemConstant.KEY_BUSINESS_TIP_UPLOAD_ATTACHMENT)) {
                detailDTO.setIsRequireUpload(true);
            }
            // 判断是否有要求对文件进行签章，并转换封装
            processSignFiles(detailDTO, processVariables, businessOperateMap);
            // 判断是否有要求选择下一节点审批人
            if (businessOperateMap.containsKey(SystemConstant.KEY_BUSINESS_TIP_CHOICE_NEXT_TASK_ASSIGNEE)) {
                detailDTO.setIsRequireNextAssignee(true);
            }
            // 设置是否需要放款验证码
            detailDTO
                .setIsLendersCaptcha(businessOperateMap.containsKey(SystemConstant.KEY_BUSINESS_TIP_LENDERS_CAPTCHA));
            // 设置是否需要放款方式
            detailDTO.setIsLoanDisbursementMethod(
                businessOperateMap.containsKey(SystemConstant.KEY_BUSINESS_TIP_LOAN_DISBURSEMENT_METHOD));
        }
        detailDTO.setStartChannel(String.valueOf(processVariables.get(WorkflowConstants.CHANNEL_SIGNAL)));
        ChannelDTO channelDetail = userCenterService.getChannelDetail(detailDTO.getStartChannel(), userId);
        detailDTO.setStartChannelName(channelDetail.getChannelName());
        // 是否有处理权限,如果任务为等待中，则不允许任何人再次操作
        if (detailDTO.getIsWait()) {
            detailDTO.setIsAccess(false);
        } else {
            detailDTO.setIsAccess(checkTaskPermission(taskId, userId));
        }

        // 是否是领取
        detailDTO.setIsClaim(taskDTO.getClaimTime() != null);
        // 记录已读状态
        readRecord(taskId, userId);
        return detailDTO;
    }

    /**
     * 处理待签章文件信息
     *
     * @param detailDTO 任务详情信息对象，处理后会重新更新该对象
     * @param processVariables 任务所属流程的流程变量池
     * @param businessOperateMap 业务操作信息
     */
    private void processSignFiles(TaskDetailDTO detailDTO, Map<String, Object> processVariables,
        Map<String, Object> businessOperateMap) {
        Map<String,
            SignActionEnum> currentTaskActionNames = SignActionEnum.actionEnumMap().entrySet().stream()
                .filter(e -> businessOperateMap.containsKey(e.getKey()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        IfpLogger.info("processSignFiles-currentTaskActionNames", "当前任务节点 {} 待签章动作参数 {}", detailDTO.getId(),
            currentTaskActionNames);
        if (currentTaskActionNames.isEmpty()) {
            detailDTO.setIsRequireSign(false);
            return;
        }
        detailDTO.setIsRequireSign(true);
        // 封装返回至前端的信息
        List<BusinessFormDataForFile> businessFormDataList = new ArrayList<>(10);
        // 获取所有待签章文件
        Object o = processVariables.get(WorkflowConstants.FLOW_LIST_OF_FILES_TO_BE_SIGNED);
        List<FilesToBeSignedBean> currentTaskFilesToBeSigned = null;
        if (o instanceof List) {
            List<FilesToBeSignedBean> filesToBeSigned = (List<FilesToBeSignedBean>)o;
            currentTaskFilesToBeSigned = filesToBeSigned.stream().filter(item -> {
                // 注意: 前期沟通（与肖苏原），需求明确说明每个签章动作在 1 个流程当中仅会出现 1 次，所以这里匹配到有签章动作，仅取当前第一个匹配到的这个即可 <br> by liulx on 2023-03-13
                // 沟通详情: 于 2022 年 12 月 7 日，居家办公，钉钉会议沟通
                ToBeSignedInfo info = item.getSignList().stream()
                    .filter(sign -> currentTaskActionNames.containsKey(sign.getSealActionName())).findFirst()
                    .orElse(null);
                if (info != null) {
                    BusinessFormDataForFile businessFormData = dozerBeanMapper.map(item, BusinessFormDataForFile.class);
                    BusinessSignInfoData signInfoData = info.getSignInfo();
                    detailDTO.setAccountId(signInfoData.getAccountId());
                    detailDTO.setSealId(signInfoData.getSealId());
                    businessFormData.setSignInfo(signInfoData);
                    businessFormDataList.add(businessFormData);
                }
                return info != null;
            }).collect(Collectors.toList());
        }
        IfpLogger.info("processSignFiles-currentTaskFilesToBeSigned", "当前任务节点 {} 待签章文件信息 {}", detailDTO.getId(),
            currentTaskFilesToBeSigned);
        detailDTO.setSignFiles(businessFormDataList);
    }

    public static void main(String[] args) {
        List<FilesToBeSignedBean> currentTaskFilesToBeSigned = null;

        Map<String, String> businessOperateMap = new HashMap<String, String>() {
            {
                put("signNodeFundingParties", "signNodeFundingParties");
                put("signNodeEnd", "signNodeEnd");
            }
        };

        Map<String,
            SignActionEnum> currentTaskActionNames = SignActionEnum.actionEnumMap().entrySet().stream()
                .filter(e -> businessOperateMap.containsKey(e.getKey()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

        String json =
            "[{\"id\":\"first-11111\",\"idName\":\"contractFile-loan\",\"displayName\":\"贷款-合约文件\",\"order\":3,\"isHidden\":false,\"isReadonly\":false,\"value\":\"zhongtai/1310878801743400963.pdf\",\"signList\":[{\"sealActionName\":\"signNodeFundingParties\",\"signInfo\":{\"accountId\":\"B49B7DCB088E4C7B993F1413ED7FBEDC\",\"sealId\":24,\"posType\":\"0\",\"posPage\":\"1\",\"posX\":180.0,\"posY\":180.0,\"key\":\"\",\"width\":100.0,\"cacellingSign\":false,\"addSignTime\":true,\"signType\":\"Single\"}},{\"sealActionName\":\"signNodeEnd\",\"signInfo\":{\"accountId\":\"6465DFBF6A1D4C32820534747419C95D\",\"sealId\":24,\"posType\":\"0\",\"posPage\":\"1\",\"posX\":280.0,\"posY\":280.0,\"key\":\"\",\"width\":120.0,\"cacellingSign\":false,\"addSignTime\":true,\"signType\":\"Single\"}}]},{\"id\":\"two-22222\",\"idName\":\"contractFile-TenancyAgreement\",\"displayName\":\"租赁合同-合约文件\",\"order\":3,\"isHidden\":false,\"isReadonly\":false,\"value\":\"zhongtai/1310878801743400963.pdf\",\"signList\":[{\"sealActionName\":\"signNodeFundingParties\",\"signInfo\":{\"accountId\":\"B49B7DCB088E4C7B993F1413ED7FBEDC\",\"sealId\":24,\"posType\":\"0\",\"posPage\":\"1\",\"posX\":180.0,\"posY\":180.0,\"key\":\"\",\"width\":100.0,\"cacellingSign\":false,\"addSignTime\":true,\"signType\":\"Single\"}},{\"sealActionName\":\"signNodeEnd\",\"signInfo\":{\"accountId\":\"A3A4C94BB2A94A389609E53614656A29\",\"sealId\":24,\"posType\":\"0\",\"posPage\":\"1\",\"posX\":280.0,\"posY\":280.0,\"key\":\"\",\"width\":120.0,\"cacellingSign\":false,\"addSignTime\":true,\"signType\":\"Single\"}}]},{\"id\":\"uuid-uuid-iii\",\"idName\":\"contractFile-TenancyAgreement\",\"displayName\":\"租赁合同-合约文件\",\"order\":3,\"isHidden\":false,\"isReadonly\":false,\"value\":\"zhongtai/1310878801743400963.pdf\",\"signList\":[{\"sealActionName\":\"signNodeFundingParties\",\"signInfo\":{\"accountId\":\"B49B7DCB088E4C7B993F1413ED7FBEDC\",\"sealId\":24,\"posType\":\"0\",\"posPage\":\"1\",\"posX\":180.0,\"posY\":180.0,\"key\":\"\",\"width\":100.0,\"cacellingSign\":false,\"addSignTime\":true,\"signType\":\"Single\"}}]},{\"id\":\"uuid-uuid-HHHH\",\"idName\":\"contractFile-TenancyAgreement\",\"displayName\":\"租赁合同-合约文件\",\"order\":3,\"isHidden\":false,\"isReadonly\":false,\"value\":\"zhongtai/1310878801743400963.pdf\",\"signList\":[{\"sealActionName\":\"signNodeEnd\",\"signInfo\":{\"accountId\":\"A3A4C94BB2A94A389609E53614656A29\",\"sealId\":24,\"posType\":\"0\",\"posPage\":\"1\",\"posX\":280.0,\"posY\":280.0,\"key\":\"\",\"width\":120.0,\"cacellingSign\":false,\"addSignTime\":true,\"signType\":\"Single\"}}]}]";

        List<FilesToBeSignedBean> filesToBeSigned = JSONArray.parseArray(json, FilesToBeSignedBean.class);

        currentTaskFilesToBeSigned = filesToBeSigned.stream().filter(item -> {
            ToBeSignedInfo info = item.getSignList().stream()
                .filter(sign -> currentTaskActionNames.containsKey(sign.getSealActionName())).findFirst().orElse(null);
            return info != null;
        }).collect(Collectors.toList());

        System.out.println("currentTaskFilesToBeSigned = " + JSON.toJSONString(currentTaskFilesToBeSigned));

    }

    /**
     * 记录用户任务是否已读并记录
     * 
     * @param taskId 任务 id
     * @param userId 用户 id
     */
    protected void readRecord(String taskId, Integer userId) {
        LambdaQueryWrapper<TaskExt> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TaskExt::getTaskId, taskId);
        queryWrapper.eq(TaskExt::getUserId, userId);
        TaskExt taskExt = taskExtMapper.selectOne(queryWrapper);
        if (taskExt == null) {
            TaskExt ext = new TaskExt();
            ext.setTaskId(taskId);
            ext.setUserId(userId);
            // 该字段不使用，已保留
            // ext.setIsClaimed(userId);
            ext.setGmtCreate(LocalDateTime.now());
            ext.setCreateBy(userId);
            ext.setGmtModified(LocalDateTime.now());
            ext.setModifiedBy(userId);
            taskExtMapper.insert(ext);
        }
    }

    /**
     * 判断任务是否已得到渠道处理结果
     * 
     * @param taskId 任务 id
     * @return 是否已处理标志
     */
    public TaskResultDTO getTaskResultForChannel(String taskId) {
        TaskResultDTO result = new TaskResultDTO();
        TaskOperateStage taskOperateStage = taskOperateStageService.getTaskOperateStageByTaskId(taskId);
        result.setIsCompleted(false);
        result.setIsToBeSigned(false);
        if (taskOperateStage == null) {
            return result;
        }

        result.setIsCompleted(taskOperateStage.getIsCompleted());
        Boolean isAllow = taskOperateStage.getIsAllow();
        if (isAllow != null) {
            result.setIsAllow(isAllow);
            result.setReason(isAllow ? null : taskOperateStage.getReason());
            result.setIsToBeSigned(taskOperateStage.getIsToBeSigned());
        }
        return result;
    }

    /**
     * 设置签章证书及印模信息 <br>
     * 注意：移动审批中，如有多个签章文件看，仅取第一个签章文件的配置，且给移动审批中的所有文件进行签章
     * 
     * @param detailDTO 任务详情信息
     */
    // private void processSignInfo(TaskDetailDTO detailDTO) {
    // List<BusinessModuleData> businessModuleDataList = detailDTO.getBusinessData();
    // if (businessModuleDataList != null && !businessModuleDataList.isEmpty()) {
    // for (BusinessModuleData moduleData : businessModuleDataList) {
    // List<BusinessGroupData> groupDataList = moduleData.getData();
    // if (groupDataList != null && !groupDataList.isEmpty()) {
    // for (BusinessGroupData groupData : groupDataList) {
    // List<BusinessFormData> formDataList = groupData.getData();
    // if (formDataList != null && !formDataList.isEmpty()) {
    // for (BusinessFormData formData : formDataList) {
    // if ("file".equals(formData.getType()) && formData.getIsSign()) {
    // BusinessSignInfoData signInfo = formData.getSignInfo();
    // detailDTO.setAccountId(signInfo.getAccountId());
    // detailDTO.setSealId(signInfo.getSealId());
    // return;
    // }
    // }
    // }
    // }
    // }
    // }
    // }
    // }

    /**
     * 查询流程是否已结束
     *
     * @param processInstanceId 流程实例 id
     * @param baseReqObj 中台基础请求参数对象
     * @author liulx
     * @date 2021-03-23 14:41:00
     */
    public ProcessInstanceDTO checkProcessEnd(String processInstanceId, BaseReqObj baseReqObj) {
        ProcessInstanceReq req = new ProcessInstanceReq();
        dozerBeanMapper.map(baseReqObj, req);
        req.setProcessInstanceId(processInstanceId);

        try {
            ServiceResult<ProcessInstanceDTO> serviceResult = processInstanceService.isFinishProcess(req);
            return serviceResult.getResult();
        } catch (BusinessException e) {
            throw new BusinessException(e.getMessage(), e.getCode());
        } catch (Exception e) {
            IfpLogger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批调用查询流程是否结束服务异常, case: {}",
                Throwables.getStackTraceAsString(e));
            throw new BusinessException("审批中心调用失败", "ECMA0001");
        }
    }

    /**
     * 查询流程当前活动的任务
     *
     * @param processInstanceId 流程实例 id
     * @param userId 当前用户 id
     */
    public ServiceResult<List<TaskDTO>> getActiveTask(String processInstanceId, Integer userId) {
        ProcessInstanceReq req = new ProcessInstanceReq();
        req.setTraceId(IDUtil.getSecureRandomId());
        req.setChannel(SystemConstant.CHANNEL);
        req.setOperationUserId(userId);
        req.setRequestTime(new Date());
        req.setProcessInstanceId(processInstanceId);
        ServiceResult<List<TaskDTO>> serviceResult = processTaskService.getActiveTaskByInstanceId(req);
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "查询当前流程 {} 活动任务：req: {}, resp: {}",
            processInstanceId, req, serviceResult);
        return serviceResult;
    }

    /**
     * 拾取任务
     *
     * @param taskId 任务 id
     * @param userId 当前用户 id
     */
    public void claim(String taskId, Integer userId) {
        ProcessTaskReq req = new ProcessTaskReq();
        req.setTaskId(taskId);
        req.setAssignee(String.valueOf(userId));
        req.setTraceId(IDUtil.getSecureRandomId());
        req.setChannel(SystemConstant.CHANNEL);
        req.setOperationUserId(userId);
        req.setRequestTime(new Date());
        ServiceResult serviceResult = processTaskService.claimTask(req);
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批【领取任务】服务，req：{}, resp: {}", req,
            serviceResult);
        if (!serviceResult.isSuccess()) {
            throw new BusinessException(serviceResult.getErrorMsg(), serviceResult.getErrorCode());
        }
    }

    /**
     * 取消拾取任务
     *
     * @param taskId 任务 id
     * @param userId 当前用户 id
     */
    public void unclaim(String taskId, Integer userId) {
        ProcessTaskReq req = new ProcessTaskReq();
        req.setAssignee(String.valueOf(userId));
        req.setTaskId(taskId);
        req.setTraceId(IDUtil.getSecureRandomId());
        req.setChannel(SystemConstant.CHANNEL);
        req.setOperationUserId(userId);
        req.setRequestTime(new Date());
        ServiceResult serviceResult = processTaskService.unclaimTask(req);
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批【取消领取任务】服务，req：{}, resp: {}",
            req, serviceResult);
        if (!serviceResult.isSuccess()) {
            throw new BusinessException(serviceResult.getErrorMsg(), serviceResult.getErrorCode());
        }
    }

    /**
     * 根据任务 id 和用户 id 查询用户是否有处理该任务的处理权限
     * 
     * @param taskId 任务 id
     * @param userId 当前用户 id
     * @return 服务调用结果
     */
    public boolean checkTaskPermission(String taskId, Integer userId) {
        ProcessTaskReq req = new ProcessTaskReq();
        req.setAssignee(String.valueOf(userId));
        req.setTaskId(taskId);
        req.setTraceId(IDUtil.getSecureRandomId());
        req.setChannel(SystemConstant.CHANNEL);
        req.setOperationUserId(userId);
        req.setRequestTime(new Date());
        ServiceResult<Boolean> serviceResult = processTaskService.checkTaskPermission(req);
        return serviceResult.isSuccess() && serviceResult.getResult();

    }

    /**
     * 执行审批中心退回服务 <br />
     * 由于退回也是退到指定的 firstNode 节点，所以也使用自定义节点跳转服务实现，该服务保留
     *
     * @param taskOperateStage 任务记录
     */
    public ServiceResult invokeApproveRecallBackToStartUserTask(TaskOperateStage taskOperateStage) {
        RecallTaskReq req = new RecallTaskReq();
        req.setAssignee(String.valueOf(taskOperateStage.getAssignee()));
        req.setCurrentTaskId(taskOperateStage.getTaskId());
        req.setTraceId(IDUtil.getSecureRandomId());
        req.setChannel(SystemConstant.CHANNEL);
        req.setOperationUserId(taskOperateStage.getAssignee());
        req.setRequestTime(new Date());
        req.setReason(taskOperateStage.getComment());

        Map<String, Object> var = new HashMap<>(2);
        // 设置移动端处理标志
        var.put(WorkflowConstants.FLOW_TASK_DONE_MOBILE_PROCESS, true);
        // 设置当前任务操作记录
        String businessOperateData = taskOperateStage.getBusinessOperateData();
        if (StringUtils.isNotBlank(businessOperateData)) {
            var.put(WorkflowConstants.FLOW_OPERATE_TASK_OPERATE_RECORD_DATA, businessOperateData);
        }
        req.setCommentFiles(StringUtils.isNotEmpty(taskOperateStage.getCommentFiles())
            ? JSON.parseArray(taskOperateStage.getCommentFiles(), CommentFile.class) : null);
        req.setVariables(var);
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "执行审批中心退回服务 req：{}", req.toString());
        return processTaskService.recallBackToStartUserTask(req);
    }

    /**
     * 执行审批中心同意服务<br>
     * 当前仅支持同意操作！
     * 
     * @param taskOperateStage 任务记录
     * @param nexAssignee 下一任务节点审批人
     * @return
     */
    public ServiceResult<Object> invokeApproveCompleteTask(TaskOperateStage taskOperateStage, String nexAssignee,
        Map<String, Object> processVariables) {
        com.ynet.middleground.approve.bean.CompleteTaskReq req =
            new com.ynet.middleground.approve.bean.CompleteTaskReq();

        req.setTaskId(taskOperateStage.getTaskId());
        req.setAssignee(String.valueOf(taskOperateStage.getAssignee()));
        req.setComment(taskOperateStage.getComment());
        req.setNextAssignee(nexAssignee);
        req.setApproveResult(OperatingTypeEnum.PASS.getCode());
        req.setTraceId(IDUtil.getSecureRandomId());
        req.setChannel(SystemConstant.CHANNEL);
        req.setOperationUserId(taskOperateStage.getAssignee());
        req.setRequestTime(new Date());

        req.setCommentFiles(StringUtils.isNotEmpty(taskOperateStage.getCommentFiles())
            ? JSON.parseArray(taskOperateStage.getCommentFiles(), CommentFile.class) : null);

        Map<String, Object> taskVar = new HashMap<>(2);
        // 设置移动端处理标志
        taskVar.put(WorkflowConstants.FLOW_TASK_DONE_MOBILE_PROCESS, true);
        // 设置当前任务操作记录
        String businessOperateData = taskOperateStage.getBusinessOperateData();
        if (StringUtils.isNotBlank(businessOperateData)) {
            taskVar.put(WorkflowConstants.FLOW_OPERATE_TASK_OPERATE_RECORD_DATA, businessOperateData);
        }
        req.setVariables(taskVar);
        req.setProcessVariables(processVariables);
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "执行审批中心同意服务 req：{}", req.toString());
        return processTaskService.completeTask(req);
    }

    /**
     * 退回至发起人，保存临时记录，并推送 MQ 消息
     *
     * @param req 退回服务请求信息
     */
    @Transactional(rollbackFor = Exception.class)
    public void recallBackToStartUserTask(RecallBackTaskReq req) {
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批【退回】服务 req：{}", req.toString());
        HistoryTaskDTO rejectTaskInfo = getRejectTaskInfo(req.getUserId(), req.getProcessInstanceId());
        if (rejectTaskInfo == null) {
            throw new BusinessException("无可退回的节点，请检查！", "ECMA0010");
        }
        TaskOperateStage operateStage = taskOperateStageService.getTaskOperateStageByTaskId(req.getTaskId());
        if (operateStage != null && operateStage.getIsAllow() == null) {
            throw new BusinessException("该任务正在处理中，请等待！", "ECMA0011");
        }
        TaskOperateStage taskOperateStage = taskOperateStageService.recallBack(req.getProcessInstanceId(),
            req.getTaskId(), req.getUserId(), req.getComment(), req.getCommentFiles());
        pushApproveBeforeMsgQueue(taskOperateStage);
    }

    /**
     * 驳回至发起人，保存临时记录，并推送 MQ 消息
     *
     * @param req 驳回服务请求信息
     */
    @Transactional(rollbackFor = Exception.class)
    public void rejectToStartUserTask(RecallBackTaskWithReasonReq req) {
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批【驳回】服务 req：{}", req.toString());
        HistoryTaskDTO rejectTaskInfo = getRejectTaskInfo(req.getUserId(), req.getProcessInstanceId());
        if (rejectTaskInfo == null) {
            throw new BusinessException("无可驳回的节点，请检查！", "ECMA0010");
        }
        TaskOperateStage operateStage = taskOperateStageService.getTaskOperateStageByTaskId(req.getTaskId());
        if (operateStage != null && operateStage.getIsAllow() == null) {
            throw new BusinessException("该任务正在处理中，请等待！", "ECMA0011");
        }
        TaskOperateStage taskOperateStage = taskOperateStageService.rejectToStartUser(req.getProcessInstanceId(),
            req.getTaskId(), req.getUserId(), req.getComment(), req.getType(), req.getCommentFiles());
        pushApproveBeforeMsgQueue(taskOperateStage);
    }

    /**
     * 提交任务，保存临时记录，并推送 MQ 消息
     *
     * @param req 提交任务请求信息
     */
    @Transactional(rollbackFor = Exception.class)
    public void completeTask(com.ynet.middleground.mobile.approve.request.CompleteTaskReq req) {
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批【提交任务】服务 req：{}",
            req.toString());
        // 通过判断是否已经发送过记录及待签章标识，判断是否是后续进入要处理待签章文件的情况
        TaskOperateStage operateStage = taskOperateStageService.getTaskOperateStageByTaskId(req.getTaskId());
        if (operateStage != null && operateStage.getIsAllow() == null) {
            throw new BusinessException("该任务正在处理中，请等待！", "ECMA0011");
        }
        if (Boolean.TRUE.equals(operateStage != null && operateStage.getIsAllow())
            && Boolean.TRUE.equals(operateStage.getIsToBeSigned())) {
            // 说明为后续进入，处理签章场景！
            taskOperateStageService.signAndAppendToBusinessOperate(req, operateStage);
            return;
        }

        TaskDetailDTO taskDetailDTO = getTaskDetailById(req.getTaskId(), req.getUserId(), null);
        TaskOperateStage taskOperateStage = taskOperateStageService.complete(req, taskDetailDTO.getIsRequireSign());
        pushApproveBeforeMsgQueue(taskOperateStage);
    }

    /**
     * 推送执行审批操作前通知
     *
     * @param taskOperateStage 审批操作临时记录信息
     */
    public void pushApproveBeforeMsgQueue(TaskOperateStage taskOperateStage) {
        sourceMobileApproveProducer.sendMessagesApproveBefore(setApproveOperateDataForMsgQueue(taskOperateStage));
    }

    /**
     * 推送执行后的消息
     *
     * @param serviceResult 调用审批中心服务后的结果
     * @param taskOperateStage 任务操作记录对象（传入该对象前，需要将更新的属性都设置进去）
     * @param operatingTypeEnum 审批操作类型
     * @return 错误提示语
     */
    public String pushApproveAfterMsgQueue(ServiceResult serviceResult, TaskOperateStage taskOperateStage,
        OperatingTypeEnum operatingTypeEnum) {
        String errorMsg = null;
        if (serviceResult != null) {
            if (serviceResult.isSuccess()) {
                taskOperateStage.setIsCompleted(true);
                taskOperateStage.setGmtModified(LocalDateTime.now());
                taskOperateStageService.updateById(taskOperateStage);
                // 只有未签章情况下才算执行完成，再推送执行后消息
                if (Boolean.FALSE.equals(taskOperateStage.getIsToBeSigned())) {
                    // 给渠道推送审批操作执行后的消息
                    pushApproveAfterMsgQueue(taskOperateStage);
                }
            } else {
                IfpLogger.error(Thread.currentThread().getStackTrace()[1].getMethodName(),
                    "MQ 执行审批中心 {} 服务异常，异常原因: {}:{}", operatingTypeEnum.getDesc(), serviceResult.getErrorCode(),
                    serviceResult.getErrorMsg());
                errorMsg = MessageFormatter.arrayFormat("MQ 执行审批中心 {} 服务异常，异常原因: {}:{}", new String[] {
                    operatingTypeEnum.getDesc(), serviceResult.getErrorCode(), serviceResult.getErrorMsg()})
                    .getMessage();
            }
        }
        return errorMsg;
    }

    /**
     * 推送执行审批操作后通知
     *
     * @param taskOperateStage 审批操作临时记录信息
     */
    public void pushApproveAfterMsgQueue(TaskOperateStage taskOperateStage) {
        Map<String, Object> data = setApproveOperateDataForMsgQueue(taskOperateStage);
        BaseReqObj baseReqObj = new BaseReqObj();
        baseReqObj.setTraceId(IDUtil.getSecureRandomId());
        baseReqObj.setChannel(SystemConstant.CHANNEL);
        baseReqObj.setOperationUserId(taskOperateStage.getCreateBy());
        baseReqObj.setRequestTime(new Date());
        ProcessInstanceDTO processInstanceDTO = checkProcessEnd((String)data.get("processInstanceId"), baseReqObj);
        data.put("processEnd", processInstanceDTO == null);
        sourceMobileApproveProducer.sendMessagesApproveAfter(data);
    }

    /**
     * 推送发送放款验证码的通知
     *
     * @param captchaCodeReq 发送放款验证码请求信息
     */
    public void pushCaptchaCodeMsgQueue(SendLendersCaptchaCodeReq captchaCodeReq) {
        TaskDTO taskInfo = getTask(captchaCodeReq.getUserId(), captchaCodeReq.getTaskId());
        Map<String, Object> processVariables = taskInfo.getProcessVariables();
        Object businessKey = processVariables.get(WorkflowConstants.BUSINESS_KEY);
        if (businessKey == null) {
            throw new BusinessException("当前流程发起时未指定关联业务标识！", "ECMA0020");
        }
        Map<String, Object> mqData = new HashMap<>(8);
        mqData.put("traceId", IDUtil.getSecureRandomId());
        mqData.put("requestTime", new Date());
        mqData.put("channel", processVariables.get(WorkflowConstants.CHANNEL_SIGNAL));
        mqData.put("businessKey", businessKey);
        mqData.put("processInstanceId", taskInfo.getProcessInstanceId());
        mqData.put("taskId", taskInfo.getId());
        mqData.put("assignee", taskInfo.getAssignee());
        sourceMobileApproveProducer.sendMessagesSendLendersCaptchaCode(mqData);
    }

    /**
     * 封装要推送的审批操作 MQ 数据
     *
     * @param taskOperateStage 审批操作临时记录信息
     * @return 审批操作前通知数据
     */
    public Map<String, Object> setApproveOperateDataForMsgQueue(TaskOperateStage taskOperateStage) {
        String secureRandomId = IDUtil.getSecureRandomId();
        QueryTaskDetailReq queryTaskDetailReq = new QueryTaskDetailReq();
        queryTaskDetailReq.setTaskId(taskOperateStage.getTaskId());
        queryTaskDetailReq.setTraceId(secureRandomId);
        queryTaskDetailReq.setChannel(SystemConstant.CHANNEL);
        queryTaskDetailReq.setRequestTime(new Date());
        queryTaskDetailReq.setOperationUserId(taskOperateStage.getCreateBy());

        TaskDTO taskDTO;
        try {
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批调用查询任务服务,reqParams: {}",
                queryTaskDetailReq.toString());
            ServiceResult<TaskDTO> serviceResultTask = processTaskService.getTask(queryTaskDetailReq);
            taskDTO = serviceResultTask.getResult();
        } catch (BusinessException e) {
            throw new BusinessException(e.getMessage(), e.getCode());
        } catch (Exception e) {
            IfpLogger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批调用查询任务服务, case: {}",
                Throwables.getStackTraceAsString(e));
            throw new BusinessException("审批中心调用失败", "ECCT0001");
        }
        // 补充记录表流程实例字段值
        taskOperateStage.setProcessInstanceId(taskDTO.getProcessInstanceId());
        Map<String, Object> taskVariables = taskDTO.getVariables();
        Map<String, Object> processVariables = taskDTO.getProcessVariables();
        Map<String, Object> mqData = new HashMap<>(32);
        mqData.put("traceId", secureRandomId);
        mqData.put("requestTime", new Date());
        mqData.put("channel", processVariables.get(WorkflowConstants.CHANNEL_SIGNAL));
        mqData.put("processInstanceId", taskDTO.getProcessInstanceId());
        mqData.put("taskId", taskOperateStage.getTaskId());
        mqData.put("businessScene", processVariables.get(WorkflowConstants.FLOW_BUSINESS_SCENE));
        mqData.put("processName", processVariables.get(WorkflowConstants.FLOW_PROCESS_NAME));
        mqData.put("taskName", taskDTO.getName());
        mqData.put("approveOperate", taskOperateStage.getOperateType());
        mqData.put("assignee", taskOperateStage.getAssignee());
        mqData.put("lendersCaptcha", taskOperateStage.getLendersCaptcha());
        mqData.put("comment", taskOperateStage.getComment());
        mqData.put("commentFiles", taskOperateStage.getCommentFiles());
        mqData.put("approveTime", taskOperateStage.getGmtCreate());
        // 从缓存记录信息中获取当前已执行的动作及数据
        String businessOperateData = taskOperateStage.getBusinessOperateData();
        mqData.put("businessOperate", businessOperateData);
        // 从任务变量池当中获取当前任务配置的所有动作参数
        Object allOperateList = taskVariables.get(WorkflowConstants.FLOW_BUSINESS_OPERATE_DATA);
        Map<String, Object> businessOperateMap;
        if (allOperateList != null) {
            mqData.put("businessOperateList", allOperateList);
            businessOperateMap = (Map<String, Object>)allOperateList;
        } else {
            businessOperateMap = new HashMap<>();
        }

        Map<String,
            SignActionEnum> currentTaskActionNames = SignActionEnum.actionEnumMap().entrySet().stream()
                .filter(e -> businessOperateMap.containsKey(e.getKey()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

        // 已执行动作参数集合
        List<String> doneBusinessOperateList = new ArrayList<>(5);
        if (StringUtils.isNotBlank(businessOperateData)) {
            JSONArray array = JSONArray.parseArray(businessOperateData);
            array.forEach(op -> {
                JSONObject opObject = (JSONObject)op;
                Object operate = opObject.get("operate");
                switch ((int)operate) {
                    case 0:
                        // 上传附件动作参数
                        doneBusinessOperateList.add(SystemConstant.KEY_BUSINESS_TIP_UPLOAD_ATTACHMENT);
                        break;
                    case 1:
                        // 签章动作参数
                        currentTaskActionNames.forEach((k, v) -> doneBusinessOperateList.add(k));
                        break;
                    case 2:
                        // 选择下一个节点审批人动作参数
                        doneBusinessOperateList.add(SystemConstant.KEY_BUSINESS_TIP_CHOICE_NEXT_TASK_ASSIGNEE);
                        break;
                    case 3:
                        // 放款方式
                        doneBusinessOperateList.add(SystemConstant.KEY_BUSINESS_TIP_LOAN_DISBURSEMENT_METHOD);
                        break;
                    default:
                }
            });
        }
        // 移动审批端已执行动作参数
        if (!doneBusinessOperateList.isEmpty()) {
            mqData.put("doneBusinessOperateList", doneBusinessOperateList);
        }
        // add by liwq on
        return mqData;
    }

}
